
PrintTable = require("SharedLibs.Utils.PrintTable");

Core = require("SharedLibs.Core");

_REFLECTION = true;

local oo = {}

_Keys = {}
_Keys["inherit"] = true;
_Keys["assemble"] = true;
_Keys["deassemble"] = true;
_Keys["extends"] = true;
_Keys["implements"] = true;
_Keys["property"] = true;
_Keys["new"] = true;

function oo.isKeyword(name)
	return _Keys[name];
end

--- 获取Obj的直接类型（非super类型）
function oo.getClass(obj)
	return getmetatable(obj);
end

--- Fill member name into map
local function _f_m(obj, cls, map)
	local s = cls.super;
	if s then
		_f_m(obj, s, map);
	end
	
	--- 读取cls里的成员（通常是方法）
	for k, v in pairs(cls) do
		map[k] = true;
	end
	
	--- 读取__m里的成员（通过assemble获取的字段）
	local m = obj.__m;
	if m then
		for k, v in pairs(m) do
			map[k] = true;
		end
	end
end

--- 获取一个对象的所有成员名字，通过一个table返回。table的key就是成员名，value始终是true（无用）
--- 包含父对象的成员
--- 注意：通过pairs只能访问到属性无法获得方法
function oo.getMembers(obj)
	local map = {};
	for k, v in pairs(obj) do
		map[k] = true;
	end
	_f_m(obj, oo.getClass(obj), map);
	return map;
end

--- 很多Check有性能损耗，所以这里加各种开关，允许上层关闭检测。
--- Interface check有性能损耗，所以提供一个开关，必要时可以关闭
oo.enableInterfaceCheck = true;
oo.enableAsyncCheck = true;
oo.notCallSuper = true;

function GetClassName(clazz)
	if clazz.__name then
		return clazz.__name;
	else
		return "UnnamedClass";
	end
end

oo.GetClassName = GetClassName;

--- 扩展一个类，给他添加方法，属性
--- 相比与继承，优点是不会损失性能，并且也更符合面向组合的设计
--- module可以是一个文件, 也可以是require后的对象
--- 注: 建议直接用XXXClass:extends
local function _extends(cls, ...)
	local modules = {...};	
	assert(cls and #modules >= 1);

	for _, module in ipairs(modules) do
		local ext = module;
		if type(module) ~= "table" then
			ext = require(module);
		end

		for _, extender in pairs(ext) do
			extender(cls);
		end
	end
end

--- 让一个cls具备组装能力. 如果supportRaw为true，则支持对Roblox Raw对象Assemble
--- 注：assemble是一个instance级别的操作
local function _make_assembable(cls, supportRaw)	
	local _OldI = nil;
	local index = cls.__index;
	if type(index) == "table" then
		_OldI = function(self, key) 
			return index[key];
		end	
	else
		_OldI = index;
	end

	--- self就是当前实例，key是要读的属性（或者方法）名
	cls.__index = function (self, key)
		local info = rawget(self, "__m")[key];
		if info then
			--- Func时有两个字段（obj和func）
			if #info == 2 then
				-- info[2]保存的是一个包装后的func
				return info[2];
			else
				-- info[1]保存的是obj
				return info[1][key];
			end
		else
			return _OldI(self, key);
		end
	end

	local _OldNewI = cls.__newindex;
	cls.__newindex = function (self, key ,value)	
 		local info = rawget(self, "__m")[key];
		if info then
			-- info[1]保存的是obj
			info[1][key] = value;
		else
			_OldNewI(self, key ,value);
		end
	end

end

--- 撤销组装
local function _cls_deassemble(self, obj, interface, ignoreMap)
	ignoreMap = ignoreMap or {};

	if not interface then
		--- 如果interface为nil，就尝试读取所有interface，然后取第一个
		local interfaces = oo.getInterfaces(obj);
		if #interfaces > 0 then
			interface = interfaces[1];
		else
			--- 如果没有借口，就把obj当interface。这里需要遍历所有父对象的成员函数（无法通过pairs访问到）
			interface = oo.getMembers(obj);	
		end
	end
	--- 遍历interface的所有属性，保存记录到__m里
	for k, v in pairs(interface) do		
		self.__m[k] = nil;
	end	
end

--- 将对象obj按interface的定义组装进当前对象
--- 组装后所有对interface的成员的访问，都将指向给obj
--- 当前对象不应该有对应的成员（即使有也会被忽略）
--- ignoreList是需要被忽略的成员的一个map，被忽略的成员仍然从cls的实例访问。
--- 注意：如果是Roblox内部对象（例如Instance），必须传一个interface，因为内部对象无法通过pairs遍历属性和方法！
local function _cls_assemble(self, obj, interface, ignoreMap)
	local cls = oo.getClass(self);
	
	if not self.__m then
		--- Member to instance map. 对每一个成员记录其对应的instance
		--- 注意：第一个调用assemble的instance的 __m 在这里创建，但之后
		--- 的instance的 __m 是在构造前就创建好了，详见oo.new
		self.__m = {};
	end

	--- 注：如果类A继承了类B，那么如果B设置了__isASM，A.__isASM也为true。
	--- 但这样是不对的，因为当访问A的一个assemble属性时，A却没有被_make_assembable，导致无法访问
	--- assemble是一个instance级别的操作，使用assemble的instance的直接类型必须被_make_assembable
	--- 所以这里必须访问cls的直接属性__isASM(使用rawget/rawset)
	if not rawget(cls, "__isASM") then
		rawset(cls, "__isASM", true);
		--- 如果需要组合Raw对象，请务必在第一个assemble时就assemble一个Raw对象
		if type(obj) == "Instance" then
			_make_assembable(cls, true);
		else
			_make_assembable(cls);
		end
	end
	
	ignoreMap = ignoreMap or {};

	if not interface then
		--- 如果interface为nil，就尝试读取所有interface，然后取第一个
		local interfaces = oo.getInterfaces(obj);
		if #interfaces > 0 then
			interface = interfaces[1];
		else
			--- 如果没有借口，就把obj当interface。这里需要遍历所有父对象的成员函数（无法通过pairs访问到）
			interface = oo.getMembers(obj);	
		end
	end
	--- 遍历interface的所有属性，保存记录到__m里
	local __m = self.__m;
	for k, v in pairs(interface) do
		if ignoreMap[k] == nil then
			if string.find(k, "__") then goto continue end;

			if oo.isKeyword(k) then goto continue end;

			local v_ = cls[k];
			if not v_ then
				--- v为nil表示没有被覆盖，就从obj里取
				if type(v) == "function" then
					local func = function(...)
						return obj[k](obj, select(2, ...));
					end
					__m[k] = {obj, func};
				else
					__m[k] = {obj};
				end
			end			
		end

		::continue::
	end		
end

---
local function _cls_inherit(clazz, parentClass)
	assert(clazz.super == nil, "Support single inheritance only!");
	clazz.super = parentClass;
	
	--- 这个vtable用于访问clazz的静态属性
	local clsVtable = {};
	setmetatable(clazz, clsVtable);

	--- 我们要确保访问class的method时，也能索引到其parentClass
	--- 当直接调用（不通过instance）一个class的method时，就会走到这里
	clsVtable.__index = function (obj, key)
		local cls = clazz;
		while cls do
			local value = rawget(cls, key);
			if value then
				return value
			else
				cls = cls.super;
			end	
		end
		return nil;
	end	
	--- metatablele类似C++里的vtable。
	--- 只能将class设置为instance的vtable，不能把class设置为class的vtable
	--- 如果将 class 的 vtable 设置为 parentClass
	--- 会导致对class的字段访问时，如果找不到，会调用 parentClass.__index
	--- 此时 __index的参数 obj 是 clazz，显然是错误的（obj必须是instance）
	---
	--- 所以，如下做法是错误的：
	---  setmetatable(clazz, parentClass)	--- 错误做法！

	return clazz;
end

local function _interface_inherit(interface, ...)
	local interfaces = {...};
	assert(#interfaces > 0, "Need provide at least one interface!");

	for _, parent in ipairs(interfaces) do
		for key, _ in pairs(parent) do
			if string.find(key, "__") then goto continue end;
			--- 原则上说, 还要过滤掉interface的"inherit"方法
			--- 但因为所有class都有inherit方法, 所以也可以不过滤

			--- 随意赋值即可
			interface[key] = 0;
		end

		::continue::
	end		
end

--- 返回一个对象的clss的名字. 如果对象的class在构造时没有传入name，那么返回nil
function oo.typeof(obj)
	return obj.__name;
end

--- 检查obj是否实现了一个接口
function oo.implemented(obj, interface)
	local clazz = getmetatable(obj);
	if not clazz then return false end;

	while clazz do
		--- 检查所有interface
		if clazz.__interfaces then
			for _, o in ipairs(clazz.__interfaces) do
				if o == interface then
					return true;
				end
			end			
		end

		clazz = clazz.super;
	end		
	return false;	
end

--- New OO support ---
--- Bind a method into "delegator"
function oo.bind(method, self)
	assert(method);
	local wrapper = function(...) 
		return method(self, ...);
	end
	return wrapper;
end

--- New OO support ---
--- 给method绑定所有参数. 也就是说, 绑定后的method无参数(直接调用 method()即可)
function oo.bindArgs(method, ...)
	assert(method);
	local args = {...};
	local wrapper = function()
		return method(table.unpack(args));
	end
	return wrapper;
end

----
local InterfaceHelper = {};
InterfaceHelper.__index = InterfaceHelper;

--- 检查Instance是否实现了interface的所有接口
function InterfaceHelper.Check(interface, instance, errList)
	if not oo.enableInterfaceCheck then return instance end;
	assert(instance); --- Check了一个nil

	local errors = nil;
	for k, v in pairs(interface) do
		if string.find(k, "__") then goto continue end;

		local member = instance[k];
		if member == nil then
			local errMsg = interface.__name.."."..k.." not implemented! \n";
			if errList then
				table.insert(errList, errMsg);
			else
				if errors == nil then errors = ""; end
				errors = errors..errMsg;
			end
		end
		::continue::
	end
	if errors then
		local clazz = getmetatable(instance);
		error(GetClassName(clazz)..": "..errors);		
	end
	return instance;
end

function InterfaceHelper.__call(interface, instance)
	return InterfaceHelper.Check(interface, instance);	
end;

--[[
	后面的...可以传入多个interface. 类似java里接口多重继承的功能.
例如:
 	local 

使用方法：
	赋值时检查类型：_instance = IXXX(inputInstance);
	显示检查：IXXX.Check(self);	
]]
function oo.interface(name)
	local info = debug.getinfo(2, "S")
	name = name or (info and info.source:match("[^/\\]*$")) or "UnnamedClass";

	local interface = {__name = name};
	setmetatable(interface, InterfaceHelper)

	interface.inherit = _interface_inherit;

	return interface;
end

function oo.implements(clazz, interface)
	assert(interface);
	assert(clazz.__interfaces);
	table.insert(clazz.__interfaces, interface);
end

function oo.getInterfaces(obj)
	local cls = oo.getClass(obj);
	return cls.__interfaces;
end

--- Construction.
--- 一个递归函数，会从最高级父类的构造函数一直调用到super的构造函数
local function _c(cls, instance, ...)
	local s = cls.super;
	if s then
		_c(s, instance, ...);
	end

	local c = rawget(cls, "constructor");
	if c then
		c(instance, ...);		
	end	
end

function oo.new(clazz, ...)
	--- metatable类似C++里的vtable。
	--- 所以，只能将clazz设置为instance的vtable
	local instance = setmetatable({}, clazz)
	
	--- __isASM为true意味着这个类已经是assembable的了
	if rawget(clazz, "__isASM") then
		rawset(instance, "__m", {});
	end		
	if instance.constructor then
		--- 调用父类及自己的构造函数
		_c(clazz, instance, ...)		
	end

	if oo.enableInterfaceCheck
		and clazz.__interfaces and clazz.__firstInstance then
		--- 仅对第一个实例检查就足够了
		clazz.__firstInstance = false;

		local errors = {};
		for _, interface in ipairs(clazz.__interfaces) do
			interface.Check(interface, instance, errors);
		end

		if #errors > 0 then
			local errMsgs = GetClassName(clazz)..": ";
			for _, errMsg in ipairs(errors) do
				errMsgs = errMsgs..errMsg;
			end
			warn(errMsgs);
		end
	end
	return instance;
end

--- For LightWeight OO support, not support property. Slightly faster than oo.richClas
--- if noReg true, will not register this class
function oo.class(name, noReg)	
	local info = debug.getinfo(2, "S")
	name = name or (info and info.source:match("[^/\\]*$"):gsub("%.lua$", "")) or "UnnamedClass";

	local clazz = {__name = name};
	clazz.__index = clazz;
	clazz.__interfaces = {};
	clazz.__firstInstance = true;

	clazz.new = function(...)
		return oo.new(clazz, ...);
	end;
	clazz.implements = oo.implements; 
	clazz.extends = _extends;
	clazz.inherit = _cls_inherit;
	clazz.assemble = _cls_assemble;
	clazz.deassemble = _cls_deassemble;
	--- 外部数据(例如从DataStore里读取出来的)只是普通的Table
	--- 需要设置一下MetaTable才能调用成员函数。
	clazz.InitMetaTable = function(self, data)	
		setmetatable(data, self);
		return data;
	end

	if not noReg then
		if _REFLECTION then Core.RegisterClass(clazz) end;		
	end

	return clazz;
end

--- 废弃, 建议用class:inherit来
function oo.inherit(parentClass, name, noReg)
	local info = debug.getinfo(2, "S")
	name = name or (info and info.source:match("[^/\\]*$")) or "UnnamedClass";

	assert(type(parentClass) == "table");
	local clazz = oo.richClass(name, noReg);

	_cls_inherit(clazz, parentClass);

	return clazz;
end

---- For Rich OO support ---
function oo.richClass(name, noReg)
	name = name or getfenv(2).script.Name;

	local clazz = {__name=name};
	clazz._propInfos = {};
	clazz.__interfaces = {};
	clazz.__firstInstance = true; 

	clazz.implements = oo.implements; 
	clazz.extends = _extends;
	clazz.inherit = _cls_inherit;
	clazz.assemble = _cls_assemble;
	clazz.deassemble = _cls_deassemble;

	--- 读取操作. 这里的obj就是self， key就是要读的属性（或者方法）名
	clazz.__index = function (obj, key)	
		--- 优先从Class里读取，可以加速方法的访问（方法只保存在Class里）
		local cls = clazz;
		while cls do
			local value = cls[key];
			if value then 
				return value
			else
				cls = cls.super;
			end	
		end

		--- 读取属性
		cls = clazz;
		while cls do
			local propInfos = cls._propInfos;
			if propInfos then
				local propInfo = propInfos[key];
				if propInfo then
					if propInfo.getter then
						return propInfo.getter(obj);
					else
						error("Property "..key.." is wirteonly!");
					end
				else
					cls = cls.super;
				end
			else
				cls = cls.super;
			end
		end	

		return nil;
	end

	--- 赋值操作
	clazz.__newindex  = function (obj, key ,value)
		--print(" +++ +++ +++++ Set: "..key)
		local clazz_ = clazz;
		while clazz_ do
			local propInfos = clazz_._propInfos;
			if propInfos then
				local propInfo = propInfos[key];
				if propInfo then
					if propInfo.setter then
						propInfo.setter(obj, value);
						return;
					else
						error("Property "..key.." is readonly! "..debug.traceback());			
					end
				else
					clazz_ = clazz_.super;
				end
			else
				clazz_ = clazz_.super;
			end
		end

		rawset(obj, key, value);
	end

	--- getterNSetter is a table that contains getter and/or setter
	clazz.property = function(propName, getterNSetter)
		clazz._propInfos[propName] = getterNSetter;
	end
	clazz.new = function(...)
		return oo.new(clazz, ...);
	end
	--- 外部数据(例如从DataStore里读取出来的)只是普通的Table
	--- 需要设置一下MetaTable才能调用成员函数。
	clazz.InitMetaTable = function(self, data)	
		setmetatable(data, self);
	end	

	if not noReg then
		if _REFLECTION then Core.RegisterClass(clazz) end;		
	end

	return clazz;
end

--- Utils --------------------
--- Extent the destModule with module
--- If override is true, will override exist symbol.
function oo.ExtentRequire(destModule, module, override)
	assert(destModule);
	assert(module);
	local temp = require(module);
	for k, v in pairs(temp)
	do
		if destModule[k] then
			if override then
				destModule[k] = v;
			else
				error("ExtentRequire failed, duplicate symbol '"..k.."'");				
			end
		else
			destModule[k] = v;
		end
	end
	return temp;
end

--- Note: Add a Forward declaration if donnot want to 
--- see the "Unknown global 'xxxx'" tip.
function oo.GlobalRequire(module, override)
	oo.ExtentRequire(getfenv(0), module, override);
end

--- 当一个Module导出多个符号时，Require可以导入指定的符号。
--- 如果没有指定名字，则默认导出和module同名的symbol
--- 如果同名symbol不存在，则该module只有包含一个symbol
--- 这个symbol将被导出。其它情况都将报错！
function oo.Require(module, symbolName)
	local temp = require(module);
	if symbolName then
		return temp[symbolName];
	else
		local symbol = temp[module.Name];
		if symbol then
			return symbol;
		else		
			local symbol = nil;
			for k, v in pairs(temp) do 
				if symbol then  warn("More than one symbol found in "..module.Name) end
				symbol = v;
			end
			if symbol then return symbol 
			else
				warn("No symbol found in "..module.Name);				
			end;
		end
	end

end

----------------------------------------------------------------------------------------------------
function oo.Class(self)
	local c = {prototype = {}}
	c.prototype.__index = c.prototype
	c.prototype._class = c;
	return c
end

function oo.New(class, ...)
	local instance = setmetatable({}, class.prototype)
	instance:constructor(...)
	return instance
end

function oo.ClassExtends(target, base)
	target._ss = base

	local staticMetatable = setmetatable({__index = base}, base)
	setmetatable(target, staticMetatable)
	local baseMetatable = getmetatable(base)
	if baseMetatable then
		if type(baseMetatable.__index) == "function" then
			staticMetatable.__index = baseMetatable.__index
		end
		if type(baseMetatable.__newindex) == "function" then
			staticMetatable.__newindex = baseMetatable.__newindex
		end
	end

	setmetatable(target.prototype, base.prototype)
	if type(base.prototype.__index) == "function" then
		target.prototype.__index = base.prototype.__index
	end
	if type(base.prototype.__newindex) == "function" then
		target.prototype.__newindex = base.prototype.__newindex
	end
	if type(base.prototype.__tostring) == "function" then
		target.prototype.__tostring = base.prototype.__tostring
	end

	target.prototype.__add = base.prototype.__add
end

--- Array ---
function oo.ArrayPush(arr, ...)
	local items = {...}
	for ____, item in ipairs(items) do
		arr[#arr + 1] = item
	end
	return #arr
end


function oo.ArrayForEach(arr, callbackFn)
	do
		local i = 0
		while i < #arr do
			callbackFn(_G, arr[i + 1], i, arr)
			i = i + 1
		end
	end
end

--- Descriptor ---
function _propIndex(self, key)
	local value = rawget(self, key)
	if value ~= nil then
		return value
	end
	local metatable = getmetatable(self)
	while metatable do
		local rawResult = rawget(metatable, key)
		if rawResult ~= nil then
			return rawResult
		end
		local descriptors = rawget(metatable, "_descriptors")
		if descriptors then
			local descriptor = descriptors[key]
			if descriptor then
				if descriptor.get then
					return descriptor.get(self)
				end
				return descriptor.value
			end
		end
		metatable = getmetatable(metatable)
	end
end
function _propNewIndex(self, key, value)
	local metatable = getmetatable(self)
	while metatable do
		local descriptors = rawget(metatable, "_descriptors")
		if descriptors then
			local descriptor = descriptors[key]
			if descriptor then
				if descriptor.set then
					descriptor.set(self, value)
				else
					if descriptor.writable == false then
						error(
							((("Cannot assign to read only property '" .. tostring(key)) .. "' of object '") .. tostring(self)) .. "'",
							0
						)
					end
					descriptor.value = value
				end
				return
			end
		end
		metatable = getmetatable(metatable)
	end
	rawset(self, key, value)
end


function oo.CloneDescriptor(____bindingPattern0)
	local enumerable
	enumerable = ____bindingPattern0.enumerable
	local configurable
	configurable = ____bindingPattern0.configurable
	local get
	get = ____bindingPattern0.get
	local set
	set = ____bindingPattern0.set
	local writable
	writable = ____bindingPattern0.writable
	local value
	value = ____bindingPattern0.value
	local descriptor = {enumerable = enumerable == true, configurable = configurable == true}
	local hasGetterOrSetter = (get ~= nil) or (set ~= nil)
	local hasValueOrWritableAttribute = (writable ~= nil) or (value ~= nil)
	if hasGetterOrSetter and hasValueOrWritableAttribute then
		error("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute.", 0)
	end
	if get or set then
		descriptor.get = get
		descriptor.set = set
	else
		descriptor.value = value
		descriptor.writable = writable == true
	end
	return descriptor
end

function oo.SetDescriptor(target, key, desc, isPrototype)
	if isPrototype == nil then
		isPrototype = false
	end
	local metatable = ((isPrototype and (function() return target end)) or (function() return getmetatable(target) end))()
	if not metatable then
		metatable = {}
		setmetatable(target, metatable)
	end
	local value = rawget(target, key)
	if value ~= nil then
		rawset(target, key, nil)
	end
	if not rawget(metatable, "_descriptors") then
		metatable._descriptors = {}
	end	
	local descriptor = oo.CloneDescriptor(desc)
	metatable._descriptors[key] = descriptor
	metatable.__index = _propIndex
	metatable.__newindex = _propNewIndex
end


--- Object ---
function oo.ObjectDefineProperty(target, key, desc)
	local luaKey = (((type(key) == "number") and (function() return key + 1 end)) or (function() return key end))()
	local value = rawget(target, luaKey)
	local hasGetterOrSetter = (desc.get ~= nil) or (desc.set ~= nil)
	local descriptor
	if hasGetterOrSetter then
		if value ~= nil then
			error(
				"Cannot redefine property: " .. tostring(key),
				0
			)
		end
		descriptor = desc
	else
		local valueExists = value ~= nil
		descriptor = {
			set = desc.set,
			get = desc.get,
			configurable = (((desc.configurable ~= nil) and (function() return desc.configurable end)) or (function() return valueExists end))(),
			enumerable = (((desc.enumerable ~= nil) and (function() return desc.enumerable end)) or (function() return valueExists end))(),
			writable = (((desc.writable ~= nil) and (function() return desc.writable end)) or (function() return valueExists end))(),
			value = (((desc.value ~= nil) and (function() return desc.value end)) or (function() return value end))()
		}
	end
	oo.SetDescriptor(target, luaKey, descriptor)
	return target
end

return oo
